use serde::{Deserialize, Serialize};
use thiserror::Error;
use std::fs;
use std::io;
use std::path::{Path, PathBuf};

#[derive(Debug, Error)]
pub enum MakeMetaError {
    #[error("JSON parsing error: {0}")]
    JsonError(#[from] serde_json::Error),
    
    #[error("Directory creation failed for '{0}': {1}")]
    DirectoryCreationFailed(PathBuf, #[source] io::Error),
    
    #[error("File write error for '{0}': {1}")]
    FileWriteError(PathBuf, #[source] io::Error),
    
    #[error("Could not extract basename from path: {0}")]
    PathError(String),
    
    #[error("General I/O error: {0}")]
    IoError(#[from] io::Error),
    
    #[error("Format error: {0}")]
    FormatError(#[from] std::fmt::Error),
}

// --- Data Structures --- (Keep as before)
#[derive(Deserialize, Debug, Clone)]
struct InputMetaItem {
    name: String,
    path: String,
    #[serde(default)]
    children: Vec<InputMetaItem>,
}

#[derive(Serialize, Deserialize, Debug, PartialEq)]
#[serde(tag = "type", rename_all = "kebab-case")]
enum SideMetaItem {
    File {
        name: String,
        #[serde(skip_serializing_if = "Option::is_none")]
        label: Option<String>,
    },
    Dir {
        name: String, // This name IS the subdirectory name
        #[serde(skip_serializing_if = "Option::is_none")]
        label: Option<String>,
        #[serde(skip_serializing_if = "Option::is_none")]
        collapsible: Option<bool>,
        #[serde(skip_serializing_if = "Option::is_none")]
        collapsed: Option<bool>,
    },
}

// --- Helper Function --- (Keep as before)
// Extracts the filename (without extension ideally) or directory name (last component)
fn get_basename(path_str: &str) -> Option<String> {
    Path::new(path_str)
        .file_name() // Gets the last component
        .and_then(|os_str| os_str.to_str()) // Convert OsStr to &str
        .map(|s| {
            Path::new(s)
                .file_stem() // Get name without extension
                .and_then(|stem| stem.to_str())
                .unwrap_or(s) // Fallback to full name if no stem or it's a dir
                .to_string()
        })
}
// ... (Keep other structs, helpers, etc. as before) ...

// --- Core Logic (Correct Implicit Dir Handling within its own meta) ---

fn generate_meta_recursive(
    items: &[InputMetaItem],
    current_target_dir: &Path,
) -> Result<(), MakeMetaError> {
    if items.is_empty() {
        return Ok(());
    }

    let mut meta_items_for_current_dir: Vec<SideMetaItem> = Vec::new();

    for item in items {
        // --- Directory Name Calculation (Simpler Rule - for Dir entries) ---
        let dir_name = Path::new(&item.path)
            .parent()
            .filter(|p| !p.as_os_str().is_empty())
            .and_then(|p| p.file_name())
            .and_then(|o| o.to_str())
            .map(String::from)
            .or_else(|| {
                item.children.first()
                    .or_else(|| Some(item))
                    .and_then(|i| Path::new(&i.path).components().next())
                    .and_then(|comp| match comp {
                        std::path::Component::Normal(os_str) => os_str.to_str(),
                        _ => None,
                    })
                    .map(String::from)
            })
            .unwrap_or_else(|| {
                println!(
                    "Warning: Could not determine directory name for item '{}' (path: {}). Using slugified label.",
                    item.name, item.path
                );
                item.name.to_lowercase()
                         .replace(|c: char| !c.is_alphanumeric() && c != '-', "")
                         .replace("--", "-")
                         .trim_matches('-')
                         .to_string()
            });

        // --- File Basename Calculation (for File entries or index check) ---
        let file_basename = get_basename(&item.path).ok_or(MakeMetaError::PathError("path error".to_string()))?;

        // --- Decision Logic ---
        if !item.children.is_empty() {
            // --- Explicit Directory (has children in input) ---
            meta_items_for_current_dir.push(SideMetaItem::Dir {
                name: dir_name.clone(), // Use the calculated dir name for the entry
                label: Some(item.name.clone()),
                collapsible: Some(true),
                collapsed: Some(true),
            });

            let next_target_dir = current_target_dir.join(&dir_name); // Target is the named subdir
            if !next_target_dir.exists() { fs::create_dir_all(&next_target_dir)?; }
            generate_meta_recursive(&item.children, &next_target_dir)?; // Recurse into the named subdir

        } else {
            // --- No Children in Input: Could be Implicit Dir's index file OR a regular file ---
            let is_index_file = file_basename == "index"; // Check if path ends like ".../index"

            // ** CHANGE HERE **
            // Regardless of whether it's an index file or not, if there are no children,
            // represent it as a FILE entry within the current directory's meta.
            meta_items_for_current_dir.push(SideMetaItem::File {
                name: file_basename.clone(), // Use the actual basename ("index" or other filename)
                label: Some(item.name.clone()),
            });
            // No recursion needed as there are no children.
            // The distinction between implicit dir and file now only matters
            // for how the PARENT item is represented in the PARENT's _meta.json.
            // This function call is already *inside* the correct target directory.
        }
    }

    // Write _meta.json for the current level
    if !meta_items_for_current_dir.is_empty() {
        if !current_target_dir.exists() { fs::create_dir_all(current_target_dir)?; }
        let meta_path = current_target_dir.join("_meta.json");
        let json_output = serde_json::to_string_pretty(&meta_items_for_current_dir)?;
        fs::write(&meta_path, json_output)?;
    }
    Ok(())
}

// --- make_meta function (Keep as before) ---
fn make_meta(target_dir: &str, json: &str) -> Result<(), MakeMetaError> {
    let root_items: Vec<InputMetaItem> = serde_json::from_str(json)?;
    let target_path = PathBuf::from(target_dir);
    if !target_path.exists() { fs::create_dir_all(&target_path)?; }
    generate_meta_recursive(&root_items, &target_path)?;
    Ok(())
}


// --- Tests (Adjust assertions for implicit dirs' content) ---
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_make_meta2() -> Result<(), MakeMetaError> {
        let target_dir_str = "D:\\test2";
        let source_dir = "E:\\project\\yesdoc\\dioxus\\docs\\zh\\learn";
        let json = fs::read_to_string("E:\\tishici\\menu.json")?;

        make_meta(target_dir_str, &json)?;
        Ok(())
    }
}